// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/accelerated_widget_mac/window_resize_helper_mac.h"

#include <stdint.h>

#include <list>

#include "base/bind.h"
#include "base/callback.h"
#include "base/macros.h"
#include "base/single_thread_task_runner.h"
#include "base/synchronization/lock.h"
#include "base/synchronization/waitable_event.h"
#include "base/threading/thread_restrictions.h"

namespace ui {
namespace {

    class WrappedTask;
    class PumpableTaskRunner;
    typedef std::list<WrappedTask*> WrappedTaskQueue;
    typedef base::Callback<void(base::WaitableEvent*, base::TimeDelta)>
        EventTimedWaitCallback;

    // A wrapper for IPCs and tasks that we may potentially execute in
    // WaitForSingleTaskToRun. Because these tasks are sent to two places to run,
    // we to wrap them in this structure and track whether or not they have run
    // yet, to avoid running them twice.
    class WrappedTask {
    public:
        WrappedTask(const base::Closure& closure, base::TimeDelta delay);
        ~WrappedTask();
        bool ShouldRunBefore(const WrappedTask& other);
        void Run();
        void AddToTaskRunnerQueue(PumpableTaskRunner* pumpable_task_runner);
        void RemoveFromTaskRunnerQueue();
        const base::TimeTicks& can_run_time() const { return can_run_time_; }

    private:
        base::Closure closure_;
        base::TimeTicks can_run_time_;
        bool has_run_;
        uint64_t sequence_number_;
        WrappedTaskQueue::iterator iterator_;

        // Back pointer to the pumpable task runner that this task is enqueued in.
        scoped_refptr<PumpableTaskRunner> pumpable_task_runner_;

        DISALLOW_COPY_AND_ASSIGN(WrappedTask);
    };

    // The PumpableTaskRunner is a task runner that will wrap tasks in an
    // WrappedTask, enqueues that wrapped task in the queue to be pumped via
    // WaitForSingleWrappedTaskToRun during resizes, and posts the task to a
    // target task runner. The posted task will run only once, either through a
    // WaitForSingleWrappedTaskToRun call or through the target task runner.
    class PumpableTaskRunner : public base::SingleThreadTaskRunner {
    public:
        PumpableTaskRunner(
            const EventTimedWaitCallback& event_timed_wait_callback,
            const scoped_refptr<base::SingleThreadTaskRunner>& target_task_runner);

        // Enqueue WrappedTask and post it to |target_task_runner_|.
        bool EnqueueAndPostWrappedTask(const tracked_objects::Location& from_here,
            WrappedTask* task,
            base::TimeDelta delay);

        // Wait at most |max_delay| to run an enqueued task.
        bool WaitForSingleWrappedTaskToRun(const base::TimeDelta& max_delay);

        // base::SingleThreadTaskRunner implementation:
        bool PostDelayedTask(const tracked_objects::Location& from_here,
            const base::Closure& task,
            base::TimeDelta delay) override;

        bool PostNonNestableDelayedTask(const tracked_objects::Location& from_here,
            const base::Closure& task,
            base::TimeDelta delay) override;

        bool RunsTasksOnCurrentThread() const override;

    private:
        friend class WrappedTask;

        ~PumpableTaskRunner() override;

        // A queue of live messages.  Must hold |task_queue_lock_| to access. Tasks
        // are added only on the IO thread and removed only on the UI thread.  The
        // WrappedTask objects are removed from the queue when they are run (by
        // |target_task_runner_| or by a call to WaitForSingleWrappedTaskToRun
        // removing them out of the queue, or by TaskRunner when it is destroyed).
        WrappedTaskQueue task_queue_;
        base::Lock task_queue_lock_;

        // Event used to wake up the UI thread if it is sleeping in
        // WaitForSingleTaskToRun.
        base::WaitableEvent event_;

        // Callback to call TimedWait on |event_| from an appropriate class.
        EventTimedWaitCallback event_timed_wait_callback_;

        scoped_refptr<base::SingleThreadTaskRunner> target_task_runner_;

        DISALLOW_COPY_AND_ASSIGN(PumpableTaskRunner);
    };

    base::LazyInstance<WindowResizeHelperMac>::Leaky g_window_resize_helper = LAZY_INSTANCE_INITIALIZER;

    ////////////////////////////////////////////////////////////////////////////////
    // WrappedTask

    WrappedTask::WrappedTask(const base::Closure& closure, base::TimeDelta delay)
        : closure_(closure)
        , can_run_time_(base::TimeTicks::Now() + delay)
        , has_run_(false)
        , sequence_number_(0)
    {
    }

    WrappedTask::~WrappedTask()
    {
        RemoveFromTaskRunnerQueue();
    }

    bool WrappedTask::ShouldRunBefore(const WrappedTask& other)
    {
        if (can_run_time_ < other.can_run_time_)
            return true;
        if (can_run_time_ > other.can_run_time_)
            return false;
        if (sequence_number_ < other.sequence_number_)
            return true;
        if (sequence_number_ > other.sequence_number_)
            return false;
        // Sequence numbers are unique, so this should never happen.
        NOTREACHED();
        return false;
    }

    void WrappedTask::Run()
    {
        if (has_run_)
            return;
        RemoveFromTaskRunnerQueue();
        has_run_ = true;
        closure_.Run();
    }

    void WrappedTask::AddToTaskRunnerQueue(
        PumpableTaskRunner* pumpable_task_runner)
    {
        pumpable_task_runner_ = pumpable_task_runner;
        base::AutoLock lock(pumpable_task_runner_->task_queue_lock_);
        static uint64_t last_sequence_number = 0;
        last_sequence_number += 1;
        sequence_number_ = last_sequence_number;
        iterator_ = pumpable_task_runner_->task_queue_.insert(
            pumpable_task_runner_->task_queue_.end(), this);
    }

    void WrappedTask::RemoveFromTaskRunnerQueue()
    {
        if (!pumpable_task_runner_.get())
            return;
        // The scope of the task runner's lock must be limited because removing
        // this reference to the task runner may destroy it.
        {
            base::AutoLock lock(pumpable_task_runner_->task_queue_lock_);
            pumpable_task_runner_->task_queue_.erase(iterator_);
            iterator_ = pumpable_task_runner_->task_queue_.end();
        }
        pumpable_task_runner_ = NULL;
    }

    ////////////////////////////////////////////////////////////////////////////////
    // PumpableTaskRunner

    PumpableTaskRunner::PumpableTaskRunner(
        const EventTimedWaitCallback& event_timed_wait_callback,
        const scoped_refptr<base::SingleThreadTaskRunner>& target_task_runner)
        : event_(false /* auto-reset */, false /* initially signalled */)
        , event_timed_wait_callback_(event_timed_wait_callback)
        , target_task_runner_(target_task_runner)
    {
    }

    PumpableTaskRunner::~PumpableTaskRunner()
    {
        // Because tasks hold a reference to the task runner, the task queue must
        // be empty when it is destroyed.
        DCHECK(task_queue_.empty());
    }

    bool PumpableTaskRunner::WaitForSingleWrappedTaskToRun(
        const base::TimeDelta& max_delay)
    {
        base::TimeTicks stop_waiting_time = base::TimeTicks::Now() + max_delay;

        for (;;) {
            base::TimeTicks current_time = base::TimeTicks::Now();
            base::TimeTicks next_task_time = stop_waiting_time;

            // Find the first task to execute in the list. This lookup takes O(n) time,
            // but n is rarely more than 2, and has never been observed to be more than
            // 12.
            WrappedTask* task_to_execute = NULL;
            {
                base::AutoLock lock(task_queue_lock_);

                for (WrappedTaskQueue::iterator it = task_queue_.begin();
                     it != task_queue_.end(); ++it) {
                    WrappedTask* potential_task = *it;

                    // If this task is scheduled for the future, take it into account when
                    // deciding how long to sleep, and continue on to the next task.
                    if (potential_task->can_run_time() > current_time) {
                        if (potential_task->can_run_time() < next_task_time)
                            next_task_time = potential_task->can_run_time();
                        continue;
                    }
                    // If there is a better candidate than this task, continue to the next
                    // task.
                    if (task_to_execute && task_to_execute->ShouldRunBefore(*potential_task)) {
                        continue;
                    }
                    task_to_execute = potential_task;
                }
            }

            if (task_to_execute) {
                task_to_execute->Run();
                return true;
            }

            // Calculate how much time we have left before we have to stop waiting or
            // until a currently-enqueued task will be ready to run.
            base::TimeDelta max_sleep_time = next_task_time - current_time;
            if (max_sleep_time <= base::TimeDelta::FromMilliseconds(0))
                break;

            event_timed_wait_callback_.Run(&event_, max_sleep_time);
        }

        return false;
    }

    bool PumpableTaskRunner::EnqueueAndPostWrappedTask(
        const tracked_objects::Location& from_here,
        WrappedTask* task,
        base::TimeDelta delay)
    {
        task->AddToTaskRunnerQueue(this);

        // Notify anyone waiting on the UI thread that there is a new entry in the
        // task map.  If they don't find the entry they are looking for, then they
        // will just continue waiting.
        event_.Signal();

        return target_task_runner_->PostDelayedTask(
            from_here, base::Bind(&WrappedTask::Run, base::Owned(task)), delay);
    }

    ////////////////////////////////////////////////////////////////////////////////
    // PumpableTaskRunner, base::SingleThreadTaskRunner implementation:

    bool PumpableTaskRunner::PostDelayedTask(
        const tracked_objects::Location& from_here,
        const base::Closure& task,
        base::TimeDelta delay)
    {
        return EnqueueAndPostWrappedTask(from_here, new WrappedTask(task, delay),
            delay);
    }

    bool PumpableTaskRunner::PostNonNestableDelayedTask(
        const tracked_objects::Location& from_here,
        const base::Closure& task,
        base::TimeDelta delay)
    {
        // The correctness of non-nestable events hasn't been proven for this
        // structure.
        NOTREACHED();
        return false;
    }

    bool PumpableTaskRunner::RunsTasksOnCurrentThread() const
    {
        return target_task_runner_->RunsTasksOnCurrentThread();
    }

} // namespace

////////////////////////////////////////////////////////////////////////////////
// WindowResizeHelperMac

scoped_refptr<base::SingleThreadTaskRunner> WindowResizeHelperMac::task_runner()
    const
{
    return task_runner_;
}

// static
WindowResizeHelperMac* WindowResizeHelperMac::Get()
{
    return g_window_resize_helper.Pointer();
}

void WindowResizeHelperMac::Init(
    const scoped_refptr<base::SingleThreadTaskRunner>& target_task_runner)
{
    DCHECK(!task_runner_);
    task_runner_ = new PumpableTaskRunner(
        base::Bind(&WindowResizeHelperMac::EventTimedWait), target_task_runner);
}

bool WindowResizeHelperMac::WaitForSingleTaskToRun(
    const base::TimeDelta& max_delay)
{
    PumpableTaskRunner* pumpable_task_runner = static_cast<PumpableTaskRunner*>(task_runner_.get());
    if (!pumpable_task_runner)
        return false;
    return pumpable_task_runner->WaitForSingleWrappedTaskToRun(max_delay);
}

WindowResizeHelperMac::WindowResizeHelperMac() { }
WindowResizeHelperMac::~WindowResizeHelperMac() { }

void WindowResizeHelperMac::EventTimedWait(base::WaitableEvent* event,
    base::TimeDelta delay)
{
    base::ThreadRestrictions::ScopedAllowWait allow_wait;
    event->TimedWait(delay);
}

} // namespace ui
